/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */
/*
 * Copyright (C) 2013 Allwinnertech, kevin.z.m <kevin@allwinnertech.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Adjustable factor-based clock implementation
 */
#ifndef __MACH_SUNXI_CLK_FACTORS_H
#define __MACH_SUNXI_CLK_FACTORS_H

#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/io.h>
#include "clk-sunxi.h"
#include "clk-sdm.h"

typedef enum pll_lock_mode {
	PLL_LOCK_NEW_MODE = 0x0,
	PLL_LOCK_OLD_MODE,
	PLL_LOCK_NONE_MODE,
	PLL_LOCK_MODE_MAX,
} pll_lock_mode_e;

/**
 * struct clk_factors_value - factor value
 *
 * @factorn:    factor-n value
 * @factork:    factor-k value
 * @factorm:    factor-m value
 * @factorp:    factor-p value
 * @factord1:   factor-d1 value
 * @factord2:   factor-d2 value
 * @frac_mode:  fraction mode value
 * @frac_freq:  fraction frequnecy value
 */
struct clk_factors_value {
	u16 factorn;
	u16 factork;

	u16 factorm;
	u16 factorp;

	u16 factord1;
	u16 factord2;

	u16 frac_mode;
	u16 frac_freq;
};


/**
 * struct sunxi_clk_factors_config - factor config
 *
 * @nshift:     shift to factor-n bit field
 * @nwidth:     width of factor-n bit field
 * @kshift:     shift to factor-k bit field
 * @kwidth:     width of factor-k bit field
 * @mshift:     shift to factor-m bit field
 * @mwidth:     width of factor-m bit field
 * @pshift:     shift to factor-p bit field
 * @pwidth:     width of factor-p bit field
 * @d1shift:    shift to factor-d1 bit field
 * @d1width:    width of factor-d1 bit field
 * @d2shift:    shift to factor-d2 bit field
 * @d2width:    width of factor-d2 bit field
 * @frac:       flag of fraction
 * @outshift:   shift to frequency select bit field
 * @modeshift:  shift to fraction/integer mode select
 * @enshift:    shift to factor enable bit field
 * @lockshift:  shift to factor lock status bit filed
 * @sdmshift:   shift to factor sdm enable bit filed
 * @sdmwidth    shift to factor sdm width bit filed
 * @sdmpat      sdmpat reg address offset
 * @sdmval      sdm default value
 * @updshift    shift to update bit (especial for ddr/ddr0/ddr1)
 * @delay       for flat factors delay.
 * @mux_inshift shift to multiplexer(multiple 24M source clocks) bit field
 * @out_enshift shift to enable pll clock output bit field
 */
struct sunxi_clk_factors_config {
	u8 nshift;
	u8 nwidth;
	u8 kshift;
	u8 kwidth;

	u8 mshift;
	u8 mwidth;
	u8 pshift;
	u8 pwidth;

	u8 d1shift;
	u8 d1width;
	u8 d2shift;
	u8 d2width;

	u8 frac;
	u8 outshift;
	u8 modeshift;
	u8 enshift;

	u8 lockshift;
	u8 sdmshift;
	u8 sdmwidth;

	unsigned long sdmpat;
	u32 sdmval;

	u32 updshift;
	u32 delay;

	u32 mux_inshift;
	u32 out_enshift;

	u64 cpu_reg;
};

struct sunxi_clk_factor_freq {
	u32 factor;
	u32 freq;
};

/**
 * struct factor_init_data - factor init data
 *
 * @name:       name of the clock
 * @parent_name:name of the parent
 * @num_parents:counter of the parents
 * @flags:      factor optimal configurations
 * @reg:        register address for the factor
 * @lock_reg:   register address for check if the pll has locked
 * @lock_bit:   bit offset of the lock_reg, to check if the the pll has locked
 * @pll_lock_ctrl_reg: pll lock control register, this function is first used on
 *              the sun50i, to enable the function of pll hardlock
 * @lock_en_bit:bit offset of the pll_lock_ctrl_reg, to enable the function
 * @config:     configuration of the factor
 * @get_factors:function for get factors parameter under a given frequency
 * @calc_rate:  function for calculate the factor frequency
 * @priv_ops:   private operations hook for the special factor
 * @priv_regops:register operation hook for read/write the register
 *
 */
struct factor_init_data {
	const char          *name;
	const char          **parent_names;
	int                 num_parents;
	unsigned long       flags;
	u64                 reg;
	u64                 lock_reg;
	unsigned char       lock_bit;
	u64                 pll_lock_ctrl_reg;
	unsigned char       lock_en_bit;
	pll_lock_mode_e     lock_mode;
	struct sunxi_clk_factors_config *config;
	int (*get_factors)(u32 rate, u32 parent_rate, struct clk_factors_value *factor);
	unsigned long (*calc_rate)(u32 parent_rate, struct clk_factors_value *factor);
	struct clk_ops *priv_ops;
	struct sunxi_reg_ops *priv_regops;
	u8			sdm_enable;
	u8			sdm_factor;
	u8			freq_mode;
	u8			sdm_freq;
};

/**
 * struct sunxi_clk_factors - factor clock
 *
 * @hw:         handle between common and hardware-specific interfaces
 * @dev:        device handle who register this clock
 * @flags:      factor optimal configurations
 * @reg:        register address for the factor
 * @lock_reg:   register address for check if the pll has locked
 * @lock_bit:   bit offset of the lock_reg, to check if the the pll has locked
 * @pll_lock_ctrl_reg: pll lock control register, this function is first used on
 *              the sun50i, to enable the function of pll hardlock
 * @lock_en_bit:bit offset of the pll_lock_ctrl_reg, to enable the function
 * @get_factor: function for get factors parameter under a given frequency
 * @calc_rate:  function for calculate the factor frequency
 * @lock:       lock for protecting the factors operations
 * @priv_ops:   private operations hook for the special factor
 *
 */
struct sunxi_clk_factors {
	struct clk_hw       hw;
	struct device       *dev;
	unsigned long       flags;
	void __iomem        *reg;
	void __iomem        *p_reg;
	void __iomem        *lock_reg;
	unsigned char       lock_bit;
	void __iomem        *pll_lock_ctrl_reg;
	unsigned char       lock_en_bit;
	pll_lock_mode_e     lock_mode;
	struct sunxi_clk_factors_config *config;
	int (*get_factors)(u32 rate, u32 parent_rate, struct clk_factors_value *factor);
	unsigned long (*calc_rate)(u32 parent_rate, struct clk_factors_value *factor);
	spinlock_t *lock;
	struct sunxi_reg_ops *priv_regops;
	u8 sdm_enable;
	u8 sdm_factor;
	u8 freq_mode;
	u8 sdm_freq;
};

struct sunxi_clk_pat_item {
	char *name;
	char *patname;
};

static inline u32 factor_readl(struct sunxi_clk_factors *factor, void __iomem *reg)
{
	return ((unsigned long *)factor->priv_regops)
			? factor->priv_regops->reg_readl(reg)
			: readl(reg);
}

static inline void factor_writel(struct sunxi_clk_factors *factor,
			unsigned int val, void __iomem *reg)
{
	(((unsigned long *)factor->priv_regops)
		? factor->priv_regops->reg_writel(val, reg)
		: writel(val, reg));
}

void sunxi_clk_get_factors_ops(struct clk_ops *ops);

struct clk *sunxi_clk_register_factors(struct device *dev,
		void __iomem *base, spinlock_t *lock,
		struct factor_init_data *init_data);

#define SUNXI_CLK_FACTORS(name, _nshift, _nwidth, _kshift, _kwidth,	\
		_mshift, _mwidth,  _pshift, _pwidth, _d1shift, _d1width, \
		_d2shift, _d2width, _frac, _outshift, _modeshift,	\
		_enshift, _sdmshift, _sdmwidth, _sdmpat, _sdmval)     \
	static struct sunxi_clk_factors_config sunxi_clk_factor_##name = {            \
		.nshift = _nshift,  \
		.nwidth = _nwidth,  \
		.kshift = _kshift,  \
		.kwidth = _kwidth,  \
		.mshift = _mshift,  \
		.mwidth = _mwidth,  \
		.pshift = _pshift,  \
		.pwidth = _pwidth,  \
		.d1shift = _d1shift,    \
		.d1width = _d1width,    \
		.d2shift = _d2shift,    \
		.d2width = _d2width,    \
		.frac = _frac,          \
		.outshift = _outshift,  \
		.modeshift = _modeshift,\
		.enshift = _enshift,    \
		.sdmshift = _sdmshift,  \
		.sdmwidth = _sdmwidth,  \
		.sdmpat = _sdmpat,      \
		.sdmval = _sdmval,      \
		.updshift = 0,           \
		.mux_inshift = 0,       \
		.out_enshift = 0,       \
		.cpu_reg = 0,  \
	}

#define SUNXI_CLK_FACTOR_CPU(name, _nshift, _nwidth, _kshift, _kwidth,	\
		_mshift, _mwidth,  _pshift, _pwidth, _d1shift, _d1width, \
		_d2shift, _d2width, _frac, _outshift, _modeshift,	\
		_enshift, _sdmshift, _sdmwidth, _sdmpat, _sdmval, _cpu_reg)     \
	static struct sunxi_clk_factors_config sunxi_clk_factor_##name = {            \
		.nshift = _nshift,  \
		.nwidth = _nwidth,  \
		.kshift = _kshift,  \
		.kwidth = _kwidth,  \
		.mshift = _mshift,  \
		.mwidth = _mwidth,  \
		.pshift = _pshift,  \
		.pwidth = _pwidth,  \
		.d1shift = _d1shift,    \
		.d1width = _d1width,    \
		.d2shift = _d2shift,    \
		.d2width = _d2width,    \
		.frac = _frac,          \
		.outshift = _outshift,  \
		.modeshift = _modeshift,\
		.enshift = _enshift,    \
		.sdmshift = _sdmshift,  \
		.sdmwidth = _sdmwidth,  \
		.sdmpat = _sdmpat,      \
		.sdmval = _sdmval,      \
		.updshift = 0,           \
		.mux_inshift = 0,       \
		.out_enshift = 0,       \
		.cpu_reg = _cpu_reg,  \
	}

#define FACTOR_ALL(nv, ns, nw, kv, ks, kw, mv, ms, mw, \
		   pv, ps, pw, d1v, d1s, d1w, d2v, d2s, d2w) \
		  ((((nv & ((1 << nw) - 1)) << ns) | \
		    ((kv & ((1 << kw) - 1)) << ks) | \
		    ((mv & ((1 << mw) - 1)) << ms) | \
		    ((pv & ((1 << pw) - 1)) << ps) | \
		    ((d1v & ((1 << d1w) - 1)) << d1s) | \
		    ((d2v & ((1 << d2w) - 1)) << d2s)))

#define FACTOR_CPU(nv, ns, nw, kv, ks, kw, mv, ms, mw, \
		   pv, ps, pw, d1v, d1s, d1w, d2v, d2s, d2w) \
		  ((((nv & ((1 << nw) - 1)) << ns) | \
		    ((kv & ((1 << kw) - 1)) << ks) | \
		    ((mv & ((1 << mw) - 1)) << ms) | \
		    ((pv & ((1 << pw) - 1)) << ps) | \
		    ((d1v & ((1 << d1w) - 1)) << d1s) | \
		    ((d2v & ((1 << d2w) - 1)) << d2s)))

#define SUNXI_CLK_FACTORS_UPDATE(name, _nshift, _nwidth, _kshift, _kwidth, \
		_mshift, _mwidth,  _pshift, _pwidth, _d1shift, _d1width, \
		_d2shift, _d2width, _frac, _outshift, _modeshift, \
		_enshift, _sdmshift, _sdmwidth, _sdmpat, _sdmval, _updshift)   \
	static struct sunxi_clk_factors_config sunxi_clk_factor_##name = {       \
		.nshift = _nshift,  \
		.nwidth = _nwidth,  \
		.kshift = _kshift,  \
		.kwidth = _kwidth,  \
		.mshift = _mshift,  \
		.mwidth = _mwidth,  \
		.pshift = _pshift,  \
		.pwidth = _pwidth,  \
		.d1shift = _d1shift,    \
		.d1width = _d1width,    \
		.d2shift = _d2shift,    \
		.d2width = _d2width,    \
		.frac = _frac,          \
		.outshift = _outshift,  \
		.modeshift = _modeshift,\
		.enshift = _enshift,    \
		.sdmshift = _sdmshift,  \
		.sdmwidth = _sdmwidth,  \
		.sdmpat = _sdmpat,      \
		.sdmval = _sdmval,      \
		.updshift = _updshift,  \
		.mux_inshift = 0, \
		.out_enshift = 0, \
		.cpu_reg = 0,  \
	}

#define SUNXI_CLK_FACTORS_DELAY(name, _nshift, _nwidth, _kshift, _kwidth, \
		_mshift, _mwidth, _pshift, _pwidth, _d1shift, _d1width, \
		_d2shift, _d2width, _frac, _outshift, _modeshift, \
		_enshift, _sdmshift, _sdmwidth, _sdmpat, _sdmval, _delay)     \
	static struct sunxi_clk_factors_config sunxi_clk_factor_##name = {	\
		.nshift = _nshift,  \
		.nwidth = _nwidth,  \
		.kshift = _kshift,  \
		.kwidth = _kwidth,  \
		.mshift = _mshift,  \
		.mwidth = _mwidth,  \
		.pshift = _pshift,  \
		.pwidth = _pwidth,  \
		.d1shift = _d1shift,    \
		.d1width = _d1width,    \
		.d2shift = _d2shift,    \
		.d2width = _d2width,    \
		.frac = _frac,          \
		.outshift = _outshift,  \
		.modeshift = _modeshift,\
		.enshift = _enshift,    \
		.sdmshift = _sdmshift,  \
		.sdmwidth = _sdmwidth,  \
		.sdmpat = _sdmpat,      \
		.sdmval = _sdmval,      \
		.delay = _delay         \
		.cpu_reg = 0,  \
	}

#define SUNXI_CLK_FACTORS1(name, _nshift, _nwidth, _kshift, _kwidth, \
		_mshift, _mwidth, _pshift, _pwidth, _d1shift, _d1width, \
		_d2shift, _d2width, _frac, _outshift, _modeshift, \
		_enshift, _sdmshift, _sdmwidth, _sdmpat, _sdmval, \
		_mux_inshift, _out_enshift)                        \
	static struct sunxi_clk_factors_config sunxi_clk_factor_##name = { \
		.nshift = _nshift,  \
		.nwidth = _nwidth,  \
		.kshift = _kshift,  \
		.kwidth = _kwidth,  \
		.mshift = _mshift,  \
		.mwidth = _mwidth,  \
		.pshift = _pshift,  \
		.pwidth = _pwidth,  \
		.d1shift = _d1shift,    \
		.d1width = _d1width,    \
		.d2shift = _d2shift,    \
		.d2width = _d2width,    \
		.frac = _frac,  \
		.outshift = _outshift,  \
		.modeshift = _modeshift,     \
		.enshift = _enshift,    \
		.sdmshift = _sdmshift,    \
		.sdmwidth = _sdmwidth,    \
		.sdmpat  = _sdmpat,    \
		.sdmval  = _sdmval,    \
		.updshift = 0,         \
		.mux_inshift = _mux_inshift, \
		.out_enshift = _out_enshift, \
		.cpu_reg = 0,  \
	}

#define FACTOR_SIZEOF(name) (sizeof(factor_pll##name##_tbl)/ \
			     sizeof(struct sunxi_clk_factor_freq))

#define FACTOR_SEARCH(name) (sunxi_clk_com_ftr_sr( \
		&sunxi_clk_factor_pll_##name, factor, \
		factor_pll##name##_tbl, index, \
		FACTOR_SIZEOF(name)))

#define F_N8X7_M0X4(nv, mv) \
	FACTOR_ALL(nv, 8, 7, 0,  0, 0, mv, 0, 4, 0,  0,  0, 0, 0, 0, 0, 0, 0)
#define F_N8X5_K4X2(nv, kv) \
	FACTOR_ALL(nv, 8, 5, kv, 4, 2, 0,  0, 0, 0,  0,  0, 0, 0, 0, 0, 0, 0)
#define F_N8X7_M0X2(nv, mv) \
	FACTOR_ALL(nv, 8, 7, 0,  0, 0, mv, 0, 2, 0,  0,  0, 0, 0, 0, 0, 0, 0)
#define F_N8X5_K4X2_M0X2(nv, kv, mv) \
	FACTOR_ALL(nv, 8, 5, kv, 4, 2, mv, 0, 2, 0,  0,  0, 0, 0, 0, 0, 0, 0)
#define F_N8X5_K4X2_M0X2_P16x2(nv, kv, mv, pv) \
	FACTOR_ALL(nv, 8, 5, kv, 4, 2, mv, 0, 2, pv, 16, 2, 0, 0, 0, 0, 0, 0)

int sunxi_clk_get_common_factors(struct sunxi_clk_factors_config *f_config,
			struct clk_factors_value *factor,
			struct sunxi_clk_factor_freq table[],
			unsigned long index, unsigned long tbl_size);

int sunxi_clk_com_ftr_sr(struct sunxi_clk_factors_config *f_config,
			struct clk_factors_value *factor,
			struct sunxi_clk_factor_freq table[],
			unsigned long index, unsigned long tbl_count);

void sunxi_clk_set_factor_lock_mode(struct factor_init_data *factor,
			const char *lock_mode);

void sunxi_clk_set_factor_sdm_info(struct factor_init_data *factor,
			struct clk_sdm_info sdm_info);
#endif
